import { applySnapshot, flow, Instance, SnapshotIn, SnapshotOut, types } from "mobx-state-tree"

import {
  getConfigs as apiGetConfigs,
  getCosUploadCredential as apiGetCosUploadCredential,
  getUser as apiGetUser,
  refreshToken as apiRefreshToken,
  saveUser as apiSaveUser,
  sendSmsCode as apiSendSmsCode,
  smsLogin as apiSmsLogin,
  visitorLogin as apiVisitorLogin,
  type GetConfigsReq,
  type GetCosUploadCredentialData,
  type GetUserData,
  type LoginData,
  type SaveUserReq,
  type SendSmsCodeReq,
  type SmsLoginReq,
} from "@/services/api/user"

import { withSetPropAction } from "./helpers/withSetPropAction"

const LoginMethodModel = types.model("LoginMethod", {
  provider: types.string,
})

const UserProfileModel = types.model("UserProfile", {
  mobileCountryCode: types.maybeNull(types.string),
  mobile: types.maybeNull(types.string),
  now: types.maybeNull(types.number),
  registerDate: types.maybeNull(types.string),
  registerAt: types.maybeNull(types.number),
  userNum: types.maybeNull(types.string),
  loginMethods: types.optional(types.array(LoginMethodModel), []),
  wallet: types.maybeNull(
    types.model({
      giftCoin: types.number,
      paidCoin: types.number,
      totalCoin: types.number,
    }),
  ),
})

type LoginMethodSnapshotIn = SnapshotIn<typeof LoginMethodModel>
type UserProfileSnapshotIn = SnapshotIn<typeof UserProfileModel>

const createEmptyProfile = (): UserProfileSnapshotIn => ({
  mobileCountryCode: null,
  mobile: null,
  now: null,
  registerDate: null,
  registerAt: null,
  userNum: null,
  loginMethods: [] as LoginMethodSnapshotIn[],
  wallet: { giftCoin: 0, paidCoin: 0, totalCoin: 0 },
})

const createInitialSnapshot = () => ({
  uuid: null,
  accessToken: null,
  refreshToken: null,
  profile: createEmptyProfile(),
})

export const UserModel = types
  .model("User", {
    uuid: types.maybeNull(types.string),
    accessToken: types.maybeNull(types.string),
    refreshToken: types.maybeNull(types.string),
    profile: types.optional(UserProfileModel, createEmptyProfile),
  })
  .actions(withSetPropAction)
  .views((self) => ({
    get isAuthenticated() {
      return Boolean(self.accessToken)
    },
  }))
  .actions((self) => {
    function setSession(data: LoginData) {
      self.uuid = data.uuid ?? null
      self.accessToken = data.accessToken ?? null
      self.refreshToken = data.refreshToken ?? null
    }

    function clearSession() {
      applySnapshot(self, createInitialSnapshot())
    }

    function setProfile(data: GetUserData) {
      self.profile.mobileCountryCode = data.mobileCountryCode ?? null
      self.profile.mobile = data.mobile ?? null
      self.profile.now = data.now ?? null
      self.profile.registerDate = data.registerDate ?? null
      self.profile.registerAt = data.registerAt ?? null
      self.profile.userNum = data.userNum ?? null
      const loginMethods = (data.loginMethods ?? []).map<LoginMethodSnapshotIn>((method) => ({
        provider: method.provider,
      }))
      self.profile.loginMethods.replace(loginMethods)
      self.profile.wallet = data.wallet ?? { giftCoin: 0, paidCoin: 0, totalCoin: 0 }
    }

    return {
      setSession,
      clearSession,
      setProfile,
    }
  })
  .actions((self) => {
    const fetchProfile = flow(function* fetchProfile() {
      const apiResponse = yield apiGetUser()
      if (apiResponse.ok && apiResponse.data?.code === 200) {
        self.setProfile(apiResponse.data.data)
        return { success: true, data: apiResponse.data.data }
      }
      return { success: false, error: apiResponse.data ?? apiResponse.problem }
    })

    const visitorLogin = flow(function* visitorLogin(uuid?: string) {
      const apiResponse = yield apiVisitorLogin(uuid ? { uuid } : undefined)
      if (apiResponse.ok && apiResponse.data?.code === 200) {
        self.setSession(apiResponse.data.data)
        yield fetchProfile()
        return { success: true, data: apiResponse.data.data }
      }
      return { success: false, error: apiResponse.data ?? apiResponse.problem }
    })

    const sendSmsCode = flow(function* sendSmsCode(payload: SendSmsCodeReq) {
      const apiResponse = yield apiSendSmsCode(payload)
      if (apiResponse.ok && apiResponse.data?.code === 200) {
        return { success: true, data: apiResponse.data.data }
      }
      return { success: false, error: apiResponse.data ?? apiResponse.problem }
    })

    const smsLogin = flow(function* smsLogin(payload: SmsLoginReq) {
      const apiResponse = yield apiSmsLogin(payload)
      if (apiResponse.ok && apiResponse.data?.code === 200) {
        self.setSession(apiResponse.data.data)
        yield fetchProfile()
        return { success: true, data: apiResponse.data.data }
      }
      return { success: false, error: apiResponse.data ?? apiResponse.problem }
    })

    const refreshSession = flow(function* refreshSession(refreshTokenOverride?: string) {
      const token = refreshTokenOverride ?? self.refreshToken
      if (!token) {
        return { success: false, error: "Missing refresh token" as const }
      }
      const apiResponse = yield apiRefreshToken({
        refreshToken: token,
      })
      if (apiResponse.ok && apiResponse.data?.code === 200) {
        self.accessToken = apiResponse.data.data.accessToken ?? null
        return { success: true, data: apiResponse.data.data }
      }
      return { success: false, error: apiResponse.data ?? apiResponse.problem }
    })

    const saveProfile = flow(function* saveProfile(payload: SaveUserReq) {
      const apiResponse = yield apiSaveUser(payload)
      if (apiResponse.ok && apiResponse.data?.code === 200) {
        yield fetchProfile()
        return { success: true }
      }
      return { success: false, error: apiResponse.data ?? apiResponse.problem }
    })

    const fetchCosCredential = flow(function* fetchCosCredential() {
      const apiResponse = yield apiGetCosUploadCredential()
      if (apiResponse.ok && apiResponse.data?.code === 200) {
        return { success: true, data: apiResponse.data.data as GetCosUploadCredentialData }
      }
      return { success: false, error: apiResponse.data ?? apiResponse.problem }
    })

    const fetchConfigs = flow(function* fetchConfigs(params?: GetConfigsReq) {
      const apiResponse = yield apiGetConfigs(params)
      if (apiResponse.ok && apiResponse.data?.code === 200) {
        return { success: true, data: apiResponse.data.data }
      }
      return { success: false, error: apiResponse.data ?? apiResponse.problem }
    })

    function logout() {
      self.clearSession()
    }

    return {
      fetchProfile,
      visitorLogin,
      sendSmsCode,
      smsLogin,
      refreshSession,
      saveProfile,
      fetchCosCredential,
      fetchConfigs,
      logout,
    }
  })

export interface User extends Instance<typeof UserModel> {}
export interface UserSnapshotOut extends SnapshotOut<typeof UserModel> {}
export interface UserSnapshotIn extends SnapshotIn<typeof UserModel> {}

export const createUserDefaultModel = () => types.optional(UserModel, () => createInitialSnapshot())
